// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package time

import (
	"errors"
	"sync"
	"syscall"
)

// go:generate env ZONEINFO=$GOROOT/lib/time/ZONEINFO。zip go run genzabbrs。go-输出zoneinfo_缩写_窗口。go 

// 一个位置将时间瞬间映射到当时使用的区域。
// 通常，位置代表地理区域中使用的时间偏移的集合。对于许多地点，时间偏移量根据当时是否使用夏令时而变化。
type Location struct {
	name string
	zone []zone
	tx   []zoneTrans

	// tzdata信息后面可以跟一个字符串，该字符串描述
	// 如何处理未记录在zoneTrans中的DST转换。
	// 格式为不带冒号的TZ环境变量；参见
	// https:
	// 示例字符串，适用于美国/洛杉矶：PST8PDT，M3。2.0，M11。1.0 
	extend string

	// 大多数查找将针对当前时间。
	// 为了避免通过tx进行二进制搜索，请保留一个
	// 静态单元素缓存，在创建位置时提供正确的
	// 区域。
	// 如果cacheStart<=t<cacheEnd，则
	// 查找可以返回cacheZone。
	// 自1970年UTC 1月1日起，cacheStart和cacheEnd的单位为秒
	// 以匹配参数
	// 以进行查找。
	cacheStart int64
	cacheEnd   int64
	cacheZone  *zone
}

// 一个区域代表一个时区，如CET。
type zone struct {
	name   string // 缩写名称，“CET”
	offset int    // UTC以东秒
	isDST  bool   // 这个区域是夏令时吗？
}

// zoneTrans代表一个时区转换。
type zoneTrans struct {
	when         int64 // 过渡时间，自1970年格林威治标准时间起以秒为单位
	index        uint8 // 当时生效的分区指数
	isstd, isutc bool  // 忽略-不知道这意味着什么
}

// 阿尔法和欧米茄是分区
// 过渡时间的开始和结束。
const (
	alpha = -1 << 63  // 数学。MinInt64 
	omega = 1<<63 - 1 // 数学。MaxInt64 
)

// UTC代表世界协调时间（UTC）。
var UTC *Location = &utcLoc

// utcLoc是独立的，这样get就可以引用&utcLoc 
// 并确保它永远不会返回零*位置，
// 即使行为不好的客户端更改了UTC。
var utcLoc = Location{name: "UTC"}

// Local表示系统的本地时区。
// 在Unix系统上，Local会参考TZ环境
// 变量来查找要使用的时区。No TZ表示
// 使用系统默认值/etc/localtime。
// TZ=”“表示使用UTC。
// TZ=“foo”表示在系统时区目录中使用文件foo。
var Local *Location = &localLoc

// localLoc是独立的，因此initLocal可以初始化
// 即使客户端更改了Local。
var localLoc Location
var localOnce sync.Once

func (l *Location) get() *Location {
	if l == nil {
		return &utcLoc
	}
	if l == &localLoc {
		localOnce.Do(initLocal)
	}
	return l
}

// 字符串返回时区信息的描述性名称，
// 对应于LoadLocation或FixedZone的name参数。
func (l *Location) String() string {
	return l.get().name
}

// FixedZone返回一个位置，该位置始终使用给定的区域名称和偏移量（UTC以东秒）。
func FixedZone(name string, offset int) *Location {
	l := &Location{
		name:       name,
		zone:       []zone{{name, offset, false}},
		tx:         []zoneTrans{{alpha, 0, false, false}},
		cacheStart: alpha,
		cacheEnd:   omega,
	}
	l.cacheZone = &l.zone[0]
	return l
}

// lookup返回在
// 瞬间使用的时区信息，以秒表示，从1970年1月1日00:00:00 UTC开始。
// 
// 返回的信息给出了分区的名称（如“CET”）、
// 该分区生效时的开始和结束时间（以秒为单位），
// UTC以东的偏移量（如-5*60*60），以及
// 当时是否遵守了夏令时。
func (l *Location) lookup(sec int64) (name string, offset int, start, end int64, isDST bool) {
	l = l.get()

	if len(l.zone) == 0 {
		name = "UTC"
		offset = 0
		start = alpha
		end = omega
		isDST = false
		return
	}

	if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
		name = zone.name
		offset = zone.offset
		start = l.cacheStart
		end = l.cacheEnd
		isDST = zone.isDST
		return
	}

	if len(l.tx) == 0 || sec < l.tx[0].when {
		zone := &l.zone[l.lookupFirstZone()]
		name = zone.name
		offset = zone.offset
		start = alpha
		if len(l.tx) > 0 {
			end = l.tx[0].when
		} else {
			end = omega
		}
		isDST = zone.isDST
		return
	}

	// 对最大时间小于等于秒的条目进行二进制搜索。
	// 不使用排序。搜索以避免依赖关系。
	tx := l.tx
	end = omega
	lo := 0
	hi := len(tx)
	for hi-lo > 1 {
		m := lo + (hi-lo)/2
		lim := tx[m].when
		if sec < lim {
			end = lim
			hi = m
		} else {
			lo = m
		}
	}
	zone := &l.zone[tx[lo].index]
	name = zone.name
	offset = zone.offset
	start = tx[lo].when
	// end=在搜索过程中维护
	isDST = zone.isDST

	// 如果我们在已知区域转换的末尾，请尝试扩展字符串。
	if lo == len(tx)-1 && l.extend != "" {
		if ename, eoffset, estart, eend, eisDST, ok := tzset(l.extend, end, sec); ok {
			return ename, eoffset, estart, eend, eisDST
		}
	}

	return
}

// lookupFirstZone返回在第一次转换时间之前，或在没有转换
// 次时要使用的时区索引。
// 
// 本地时间的参考实现。c来自
// https:
// 为这些情况实现以下算法：
// 1）如果第一个区域未被转换使用，请使用它。
// 2）否则，如果有过渡时间，并且第一个
// 过渡到白天的某个区域，则在第一个过渡
// 非白天时区。
// 区域之前和最近的位置找到第一个
// 3）否则，如果
// 有一个区域，则使用第一个非白天区域。否则，使用第一个区域。
func (l *Location) lookupFirstZone() int {
	// 案例一。
	if !l.firstZoneUsed() {
		return 0
	}

	// 案例二。
	if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST {
		for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- {
			if !l.zone[zi].isDST {
				return zi
			}
		}
	}

	// 案例三。
	for zi := range l.zone {
		if !l.zone[zi].isDST {
			return zi
		}
	}

	// 案例四。
	return 0
}

// firstZoneUsed报告第一个区域是否被某些
// 转换使用。
func (l *Location) firstZoneUsed() bool {
	for _, tx := range l.tx {
		if tx.index == 0 {
			return true
		}
	}
	return false
}

// tzset采用类似于在TZ环境中找到的时区字符串
// 变量，最后一次时区转换的结束时间表示为秒
// 自1970年1月1日00:00:00 UTC以来，时间以同样的方式表示。
// 我们称之为tzset字符串，因为在C中，函数tzset读取TZ。
// 返回值与查找相同，加上ok，它报告
// 解析是否成功。
func tzset(s string, initEnd, sec int64) (name string, offset int, start, end int64, isDST, ok bool) {
	var (
		stdName, dstName     string
		stdOffset, dstOffset int
	)

	stdName, s, ok = tzsetName(s)
	if ok {
		stdOffset, s, ok = tzsetOffset(s)
	}
	if !ok {
		return "", 0, 0, 0, false, false
	}

	// tzset字符串中的数字被添加到本地时间以获得UTC，
	// 但是我们的偏移量被添加到UTC以获得本地时间，
	// 所以我们否定了我们在这里看到的数字。
	stdOffset = -stdOffset

	if len(s) == 0 || s[0] == ',' {
		// 没有夏令时。
		return stdName, stdOffset, initEnd, omega, false, true
	}

	dstName, s, ok = tzsetName(s)
	if ok {
		if len(s) == 0 || s[0] == ',' {
			dstOffset = stdOffset + secondsPerHour
		} else {
			dstOffset, s, ok = tzsetOffset(s)
			dstOffset = -dstOffset // 与stdOffset一样，在上面
		}
	}
	if !ok {
		return "", 0, 0, 0, false, false
	}

	if len(s) == 0 {
		// 每个tzcode的默认DST规则。
		s = ",M3.2.0,M11.1.0"
	}
	// TZ的定义没有提到“；”但tzcode接受了它。
	if s[0] != ',' && s[0] != ';' {
		return "", 0, 0, 0, false, false
	}
	s = s[1:]

	var startRule, endRule rule
	startRule, s, ok = tzsetRule(s)
	if !ok || len(s) == 0 || s[0] != ',' {
		return "", 0, 0, 0, false, false
	}
	s = s[1:]
	endRule, s, ok = tzsetRule(s)
	if !ok || len(s) > 0 {
		return "", 0, 0, 0, false, false
	}

	year, _, _, yday := absDate(uint64(sec+unixToInternal+internalToAbsolute), false)

	ysec := int64(yday*secondsPerDay) + sec%secondsPerDay

	// 以秒为单位计算Unix时代以来的年初。
	d := daysSinceEpoch(year)
	abs := int64(d * secondsPerDay)
	abs += absoluteToInternal + internalToUnix

	startSec := int64(tzruleTime(year, startRule, stdOffset))
	endSec := int64(tzruleTime(year, endRule, dstOffset))
	dstIsDST, stdIsDST := true, false
	// 注意：这是“DST”和“STD”的翻转，同时保留标签
	// 这发生在南半球。因此，这里的标签有点不符合目标。
	if endSec < startSec {
		startSec, endSec = endSec, startSec
		stdName, dstName = dstName, stdName
		stdOffset, dstOffset = dstOffset, stdOffset
		stdIsDST, dstIsDST = dstIsDST, stdIsDST
	}

	// 我们返回的起始值和结束值是准确的
	// 接近夏令时转换，但在其他方面
	// 只是今年的开始和结束。这对
	// 唯一关心的来电者就足够了，那就是Date。
	if ysec < startSec {
		return stdName, stdOffset, abs, startSec + abs, stdIsDST, true
	} else if ysec >= endSec {
		return stdName, stdOffset, endSec + abs, abs + 365*secondsPerDay, stdIsDST, true
	} else {
		return dstName, dstOffset, startSec + abs, endSec + abs, dstIsDST, true
	}
}

// tzsetName返回tzset字符串s开头的时区名称、
// 以及s的其余部分，并报告解析是否正常。chuang jian defg
func tzsetName(s string) (string, string, bool) {
	if len(s) == 0 {
		return "", "", false
	}
	if s[0] != '<' {
		for i, r := range s {
			switch r {
			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ',', '-', '+':
				if i < 3 {
					return "", "", false
				}
				return s[:i], s[i:], true
			}
		}
		if len(s) < 3 {
			return "", "", false
		}
		return s, "", true
	} else {
		for i, r := range s {
			if r == '>' {
				return s[1:i], s[i+1:], true
			}
		}
		return "", "", false
	}
}

// 时区偏移量以秒为单位返回。
func tzsetOffset(s string) (offset int, rest string, ok bool) {
	if len(s) == 0 {
		return 0, "", false
	}
	neg := false
	if s[0] == '+' {
		s = s[1:]
	} else if s[0] == '-' {
		s = s[1:]
		neg = true
	}

	// tzdata代码在这里允许的值最多为24*7，
	// 尽管POSIX不允许。
	var hours int
	hours, s, ok = tzsetNum(s, 0, 24*7)
	if !ok {
		return 0, "", false
	}
	off := hours * secondsPerHour
	if len(s) == 0 || s[0] != ':' {
		if neg {
			off = -off
		}
		return off, s, true
	}

	var mins int
	mins, s, ok = tzsetNum(s[1:], 0, 59)
	if !ok {
		return 0, "", false
	}
	off += mins * secondsPerMinute
	if len(s) == 0 || s[0] != ':' {
		if neg {
			off = -off
		}
		return off, s, true
	}

	var secs int
	secs, s, ok = tzsetNum(s[1:], 0, 59)
	if !ok {
		return 0, "", false
	}
	off += secs

	if neg {
		off = -off
	}
	return off, s, true
}

// ruleKind是可以在tzset字符串中看到的规则类型。
type ruleKind int

const (
	ruleJulian ruleKind = iota
	ruleDOY
	ruleMonthWeekDay
)

// 规则是从tzset字符串读取的规则。
type rule struct {
	kind ruleKind
	day  int
	week int
	mon  int
	time int // 转换时间
}

// tzsetRule从tzset字符串解析规则。
// 返回规则和字符串的其余部分，并报告成功。
func tzsetRule(s string) (rule, string, bool) {
	var r rule
	if len(s) == 0 {
		return rule{}, "", false
	}
	ok := false
	if s[0] == 'J' {
		var jday int
		jday, s, ok = tzsetNum(s[1:], 1, 365)
		if !ok {
			return rule{}, "", false
		}
		r.kind = ruleJulian
		r.day = jday
	} else if s[0] == 'M' {
		var mon int
		mon, s, ok = tzsetNum(s[1:], 1, 12)
		if !ok || len(s) == 0 || s[0] != '.' {
			return rule{}, "", false

		}
		var week int
		week, s, ok = tzsetNum(s[1:], 1, 5)
		if !ok || len(s) == 0 || s[0] != '.' {
			return rule{}, "", false
		}
		var day int
		day, s, ok = tzsetNum(s[1:], 0, 6)
		if !ok {
			return rule{}, "", false
		}
		r.kind = ruleMonthWeekDay
		r.day = day
		r.week = week
		r.mon = mon
	} else {
		var day int
		day, s, ok = tzsetNum(s, 0, 365)
		if !ok {
			return rule{}, "", false
		}
		r.kind = ruleDOY
		r.day = day
	}

	if len(s) == 0 || s[0] != '/' {
		r.time = 2 * secondsPerHour // 2am是默认的
		return r, s, true
	}

	offset, s, ok := tzsetOffset(s[1:])
	if !ok {
		return rule{}, "", false
	}
	r.time = offset

	return r, s, true
}

// tzsetNum从tzset字符串中解析数字。
// 返回数字和字符串的其余部分，并报告成功。
// 数字必须介于最小值和最大值之间。
func tzsetNum(s string, min, max int) (num int, rest string, ok bool) {
	if len(s) == 0 {
		return 0, "", false
	}
	num = 0
	for i, r := range s {
		if r < '0' || r > '9' {
			if i == 0 || num < min {
				return 0, "", false
			}
			return num, s[i:], true
		}
		num *= 10
		num += int(r) - '0'
		if num > max {
			return 0, "", false
		}
	}
	if num < min {
		return 0, "", false
	}
	return num, "", true
}

// tzruleTime需要一年、一条规则和一个时区偏移量，
// 并返回自一年开始
// 规则生效以来的秒数。
func tzruleTime(year int, r rule, off int) int {
	var s int
	switch r.kind {
	case ruleJulian:
		s = (r.day - 1) * secondsPerDay
		if isLeap(year) && r.day >= 60 {
			s += secondsPerDay
		}
	case ruleDOY:
		s = r.day * secondsPerDay
	case ruleMonthWeekDay:
		// 泽勒同余。
		m1 := (r.mon+9)%12 + 1
		yy0 := year
		if r.mon <= 2 {
			yy0--
		}
		yy1 := yy0 / 100
		yy2 := yy0 % 100
		dow := ((26*m1-2)/10 + 1 + yy2 + yy2/4 + yy1/4 - 2*yy1) % 7
		if dow < 0 {
			dow += 7
		}
		// 现在道琼斯指数是周一第一天的星期几。
		// 获取道琼斯指数第一天的月份日期。
		d := r.day - dow
		if d < 0 {
			d += 7
		}
		for i := 1; i < r.week; i++ {
			if d+7 >= daysIn(Month(r.mon), year) {
				break
			}
			d += 7
		}
		d += int(daysBefore[r.mon-1])
		if isLeap(year) && r.mon > 2 {
			d++
		}
		s = d * secondsPerDay
	}

	return s + r.time - off
}

// lookupName返回给定伪Unix时间的时区信息（如“EST”）（一天中的给定时间为UTC）。
func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) {
	l = l.get()

	// 首先尝试使用正确名称的区域，该区域实际上是在给定时间生效的
	// 区域。（在澳大利亚悉尼，标准的
	// 和夏令时都缩写为“EST”。使用
	// 偏移量可以帮助我们在给定的时间内选择正确的偏移量。
	// 这并不完美：在向后过渡期间，我们可能会选择
	// 其中一个。）
	for i := range l.zone {
		zone := &l.zone[i]
		if zone.name == name {
			nam, offset, _, _, _ := l.lookup(unix - int64(zone.offset))
			if nam == zone.name {
				return offset, true
			}
		}
	}

	// 否则返回到普通名称匹配。
	for i := range l.zone {
		zone := &l.zone[i]
		if zone.name == name {
			return zone.offset, true
		}
	}

	// 否则，放弃。
	return
}

// 注意（rsc）：最终我们也需要接受POSIX TZ环境
// 语法，但我今天不想实现它。

var errLocation = errors.New("time: invalid location name")

var zoneinfo *string
var zoneinfoOnce sync.Once

// LoadLocation返回具有给定名称的位置。
// 
// 如果名称为“”或“UTC”，LoadLocation将返回UTC。
// 如果名称为“Local”，则LoadLocation返回Local。
// 
// 否则，该名称将被视为与IANA时区数据库中的文件
// 对应的位置名，例如“America/New_York”。
// 
// LoadLocation在以下
// 按顺序排列的位置：
// 
// -由ZONEINFO环境变量
// -在Unix系统上，系统标准安装位置
// -$GOROOT/lib/time/ZONEINFO命名的目录或未压缩的zip文件。zip 
// -时间/tzdata包，如果是导入的
func LoadLocation(name string) (*Location, error) {
	if name == "" || name == "UTC" {
		return UTC, nil
	}
	if name == "Local" {
		return Local, nil
	}
	if containsDotDot(name) || name[0] == '/' || name[0] == '\\' {
		// 没有有效的IANA时区名称包含单个点，
		// 更少的点。同样，没有一个是以斜线开头的。
		return nil, errLocation
	}
	zoneinfoOnce.Do(func() {
		env, _ := syscall.Getenv("ZONEINFO")
		zoneinfo = &env
	})
	var firstErr error
	if *zoneinfo != "" {
		if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil {
			if z, err := LoadLocationFromTZData(name, zoneData); err == nil {
				return z, nil
			}
			firstErr = err
		} else if err != syscall.ENOENT {
			firstErr = err
		}
	}
	if z, err := loadLocation(name, zoneSources); err == nil {
		return z, nil
	} else if firstErr == nil {
		firstErr = err
	}
	return nil, firstErr
}

// containsDotDot报告s是否包含“.”。
func containsDotDot(s string) bool {
	if len(s) < 2 {
		return false
	}
	for i := 0; i < len(s)-1; i++ {
		if s[i] == '.' && s[i+1] == '.' {
			return true
		}
	}
	return false
}
